package com.guozcc.bootweb.platform.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.guozcc.bootweb.platform.annotation.Trade;
import com.guozcc.bootweb.platform.common.LogionCache;
import com.guozcc.bootweb.platform.common.ResultUtils;
import com.guozcc.bootweb.platform.common.TradeErrorCode;
import com.guozcc.bootweb.platform.common.HttpContextUtils;
import com.guozcc.bootweb.platform.config.exception.TradeException;
import com.guozcc.bootweb.platform.dto.request.BaseRequestDto;
import com.guozcc.bootweb.platform.dto.response.ResultVO;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;


@Component
@Aspect//定义通知、切点关系
@Order(value = 1)//多个切面的执行顺序  执行顺序从小到大
public class TradeAspect{

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Pointcut("@annotation(com.guozcc.bootweb.platform.annotation.Trade)")
    public void tradeAspectPointcut(){}

    @Around("tradeAspectPointcut()")
    public Object trade(ProceedingJoinPoint joinPoint) throws Throwable {

        long begainTime = System.currentTimeMillis();

        //设置日志追踪索引
        //MDC.get("traceId");
        //MDC.put("traceId", UUID.randomUUID().toString().replace("-",""));


        //获取HttpServletRequest
        HttpServletRequest httpServletRequest = HttpContextUtils.getHttpServletRequest();
        final String token = httpServletRequest.getHeader("authorization");

        //获取类、方法以及注解相关信息
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        Method method = signature.getMethod();
        Trade autoAnnotation = method.getAnnotation(Trade.class);
        boolean checkLogin = autoAnnotation.checkLogin();

        log.info("请求接口编码tradeCode="+autoAnnotation.tradeCode());
        log.info("是否校验登录checkLogin="+autoAnnotation.checkLogin());
        Class clazz = method.getReturnType();

        //获取方法入参信息,打印
        Object[] args = joinPoint.getArgs();
        this.printRequestLog(args);

        //登录鉴权校验
        if(checkLogin){
            Object checkRes = this.checkLogin(token,clazz);
            if(!"pass".equals(checkRes)){
                return checkRes;
            }
        }



        //执行目标方法
        Object result = null;
        try {
            result = joinPoint.proceed(args);
        }catch (TradeException e){
            result = this.failRes(e.getEc(),e.getEm(),clazz);
        }

        //打印最终响应报文
        this.printResponseLog(result,begainTime);

        //放行
        return result;
    }


    /*
     * 组响应报文
     */
    private Object failRes(String errcode,String errmsg,Class clazz){
        log.info("响应类型"+clazz);
        if(ResultVO.class.equals(clazz)){
            return ResultUtils.fail(errcode,errmsg);
        }else if(ResponseEntity.class.equals(clazz)){
            byte[] res = JSON.toJSONString(ResultUtils.fail(errcode,errmsg)).getBytes();
            return ResponseEntity
                    .ok()
                    .headers(new HttpHeaders(){{
                        add("Cache-Control","no-cache,no-store,must-revalidate");
                        add("Content-Disposition","attachment;filename=targetImportTemplate.xlsx");
                        add("Pragma","no-cache");
                        add("Expires","0");
                    }})
                    .contentLength(res.length)
                    .contentType(MediaType.parseMediaType("application/cotet-stream"))
                    .body(new ByteArrayResource(res));
        }else {
            return ResultUtils.fail(errcode,errmsg);
        }
    }

    /**
     * 打印请求参数
     */
    private void printRequestLog(Object[] args){
        try {
            if(args!=null){
                if(args.length==0){
                    log.info("无上送参数");
                }else if(args.length==1){
                    if(args[0] ==null){
                        log.info("参数为空");
                    }else if(args[0] instanceof BaseRequestDto){
                        log.info("接口请求<BaseRequestDto>:"+JSON.toJSONString(args[0]));
                    }else if(args[0] instanceof JSONObject){
                        log.info("接口请求<JSONObject>:"+((JSONObject)args[0]).toJSONString());
                    }else if(args[0] instanceof MultipartFile){
                        log.info("接口请求<MultipartFile>:文件信息");
                    }else{
                        log.info("接口请求<"+args[0].getClass().getName()+">:"+args[0]);
                    }
                }else if(args.length>=1){
                    log.info("上送多个参数======");
                    for(int i=0;i<args.length;i++){
                        if(args[i] ==null){
                            log.info("参数为空"+i);
                        }else if(args[i] instanceof BaseRequestDto){
                            log.info("接口请求参数"+i+"<BaseRequestDto>:"+JSON.toJSONString(args[i]));
                        }else if(args[i] instanceof JSONObject){
                            log.info("接口请求参数"+i+"<JSONObject>:"+((JSONObject)args[i]).toJSONString());
                        }else if(args[i] instanceof MultipartFile){
                            log.info("接口请求参数"+i+"<MultipartFile>:上传文件");
                        }else{
                            log.info("接口请求参数"+i+"<"+args[i].getClass().getName()+">:"+args[i]);
                        }
                    }
                    log.info("上送多个参数======");
                }
            }
        }catch (Exception e){
            log.warn("打印请求参数异常",e);
        }
    }

    /**
     * 打印最终响应报文
     */
    private void printResponseLog(Object result,long begainTime){
        long subTime = System.currentTimeMillis()-begainTime;
        try {
            if(result instanceof ResponseEntity){
                log.info("接口耗时["+subTime+"ms],返回<ResponseEntity>:文件");
            }else if(result instanceof ResultVO){
                log.info("接口耗时["+subTime+"ms],返回<ResultVO>:"+JSON.toJSONString(result));
            }else if(result instanceof JSONObject){
                log.info("接口耗时["+subTime+"ms],返回<ResultVO>:"+JSON.toJSONString(result));
            }else{
                log.info("接口耗时["+subTime+"ms],返回<"+result.getClass().getName()+">:"+result.toString());
            }
        }catch (Exception e){
            log.warn("打印报文异常",e);
        }
    }

    /*
     * 登录校验
     */
    private Object checkLogin(String token,Class clazz){
        if(StringUtils.isEmpty(token)){
            log.info("未检测到token，拦截");
            return failRes(TradeErrorCode.TRADE_LOGIN_CHECK_FAIL.getErrcode(),"您尚未登录，请先登录",clazz);
        }
        if(!token.contains("@#@#@#")){
            return failRes(TradeErrorCode.TRADE_LOGIN_CHECK_FAIL.getErrcode(),"登录令牌不合法，请重新登录",clazz);
        }
        String [] tokenInfo = token.split("@#@#@#");
        if(tokenInfo.length!=2){
            return failRes(TradeErrorCode.TRADE_LOGIN_CHECK_FAIL.getErrcode(),"登录令牌不合法，请重新登录",clazz);
        }
        String userCode = tokenInfo[0];
        String userToken = tokenInfo[1];
        ConcurrentHashMap<String, JSONObject> userMap = LogionCache.getInstance().get(userCode);
        if(userMap==null||userMap.get(userToken)==null){
            return failRes(TradeErrorCode.TRADE_LOGIN_CHECK_FAIL.getErrcode(),"登录已失效，请重新登录",clazz);
        }
        JSONObject cacheuserInfo = userMap.get(userToken).getJSONObject("userInfo");
        if(cacheuserInfo==null||StringUtils.isEmpty(cacheuserInfo.getLong("refTime"))){
            return failRes(TradeErrorCode.TRADE_LOGIN_CHECK_FAIL.getErrcode(),"您尚未登录，请先登录",clazz);
        }
        long tokenTime=cacheuserInfo.getLong("refTime");
        long currentTime=System.currentTimeMillis();
        if((currentTime-tokenTime)>1000*60*300){
            LogionCache.getInstance().remove(userCode);
            log.info("tokenTime="+tokenTime);
            log.info("currentTime="+currentTime);
            log.info("currentTime-tokenTime =:"+(currentTime-tokenTime));
            return failRes(TradeErrorCode.TRADE_LOGIN_CHECK_FAIL.getErrcode(),"您的令牌已过期，请重新登录",clazz);
        }else{
            cacheuserInfo.put("refTime",currentTime);//设置刷新时间
            return "pass";
        }
    }





//        多个切面执行顺序
//        com.guozcc.bootweb.platform.aop.TradeAspect: Around before 1 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect: before 1 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect1: Around before 2 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect1: before 2 ......
//        目标方法日志。。。。。
//        com.guozcc.bootweb.platform.aop.TradeAspect1: afterReturning 2 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect1: after 2 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect1: Around after 2 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect: afterReturning 1 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect: after 1 ......
//        com.guozcc.bootweb.platform.aop.TradeAspect: Around after 1 ......

//    @Before("tradeAspectPointcut()")
//    public void before() {
//        log.info("before 1 ......");
//    }
//
//    @After("tradeAspectPointcut()")
//    public void after() {
//        log.info("after 1 ......");
//    }
//
//    @AfterThrowing("tradeAspectPointcut()")
//    public void afterThrowing() {
//        log.info("afterThrowing 1 ......");
//    }
//
//    @AfterReturning("tradeAspectPointcut()")
//    public void afterReturning() {
//        log.info("afterReturning 1 ......");
//    }

}
